package com.netease.demo.shortvideo.uploader;

import android.app.Service;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.netease.cloud.nos.android.core.CallRet;
import com.netease.cloud.nos.android.exception.InvalidParameterException;
import com.netease.demo.shortvideo.DemoCache;
import com.netease.demo.shortvideo.model.VideoItem;
import com.netease.demo.shortvideo.server.DemoServerHttpClient;
import com.netease.demo.shortvideo.server.entity.AddVideoResponseEntity;
import com.netease.demo.shortvideo.uploader.constant.UploadState;
import com.netease.demo.shortvideo.uploader.model.UploadDbHelper;
import com.netease.vcloudnosupload.UploadBuilder;
import com.netease.vcloudnosupload.VcloudUpload;

import org.json.JSONException;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by zhukkun on 3/1/17.
 */
public class UploadService extends Service {

    public static final boolean show_nofitycation = false; //由于开启通知, 用户点击通知时,导航路径未明确,故暂时关闭通知.
    public static final int POLLING_INTERVAL = 30000; //30s一次的轮询,查询转码状态 用户可根据自己需要调整时长间隔

    public static final String UPLAOD_CALL_BACK = "vod/uploadcallback";
    public static final String TRANSCODE_CALL_BACK = "vod/transcodecallback";

    public static final int TRANSCODE_THREAD_STATE_WAIT = 0;
    public static final int TRANSCODE_THREAD_STATE_WORKING = 1;

    private UploadController controller;
    private VcloudUpload vcloudUpload;

    //用于处理上传任务的线程
    private HandlerThread uploadThread;
    //用于轮询转码状态的线程
    private HandlerThread transCodeQueryThread;
    //上传任务发送器, 发送任务后会在上传任务线程处理
    private Handler uploadHandler;
    //轮询任务发送器, 发送任务后会在转码查询线程处理
    private Handler transCodeQueryHandler;

    private UploadBinder mBinder = new UploadBinder();

    private int transcode_thread_state =  TRANSCODE_THREAD_STATE_WAIT;

    private VideoItem uploadingItem; //当前正在上传的item
    private boolean needStopUpload; //是否需要停止上传

    private Set<VideoItem> waitAddToServerItems = new HashSet<>(); //避免反复添加至应用服务器


    @Override
    public void onCreate() {
        super.onCreate();
        uploadThread = new HandlerThread("UploadService");
        uploadThread.start();
        uploadHandler = new Handler(uploadThread.getLooper());

        transCodeQueryThread = new HandlerThread("transCodeQueryThread");
        transCodeQueryThread.start();
        transCodeQueryHandler = new Handler(transCodeQueryThread.getLooper());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(vcloudUpload !=null) {
            vcloudUpload.cancelTask();
        }
        uploadThread.quit();
        transCodeQueryThread.quit();
    }

    public class UploadBinder extends Binder{

        public void attachController(UploadController controller){
            UploadService.this.controller = controller;
        }

        public void startUpload(){
            //使用handler发送任务, 保证上传任务是在上传线程中串行执行,这样即使同时收到多个开启上传任务的请求,也会串行执行,并且由于只有Wait状态的任务才会进入uploadOne(),故可避免任务的重复上传
            needStopUpload = false;
            uploadHandler.post(new Runnable() {
                @Override
                public void run() {
                    UploadService.this.startUpload();
                }
            });
        }

        public void deleteUploadItem(VideoItem videoItem){
            if(vcloudUpload!=null && uploadingItem!=null && videoItem.getId().equals(uploadingItem.getId())){
                vcloudUpload.cancelTask();
            }
        }

        public void stopUpload(){
            if(vcloudUpload!=null){
                transCodeQueryHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        vcloudUpload.cancelTask();
                        //由于uploadThread中执行的uploadOne()sleep了200ms,所以即使任务关闭,此时的uploadItem也不为null.
                        if(uploadingItem !=null) {
                            uploadingItem.setState(UploadState.STATE_WAIT);
                            controller.onReceiveUploadCallback(uploadingItem.getId(), uploadingItem.getUploadProgress(), UploadState.STATE_WAIT);
                            UploadDbHelper.updateToDb(uploadingItem);
                        }
                    }
                });
            }
            needStopUpload = true;
        }

        public boolean isUploading() {
            return uploadingItem!=null;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        needStopUpload = false;
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if(vcloudUpload !=null) {
            vcloudUpload.cancelTask();
        }
        needStopUpload = true;
        return super.onUnbind(intent);
    }


    private void startUpload() {
        if(controller == null){
            throw new NullPointerException("must attachController() before startUpload()");
        }

        List<VideoItem> waitUploadingItems = new ArrayList<>(controller.getDataAccessor().getUploadList());

        for (int i = 0; i < waitUploadingItems.size(); i++) {
            VideoItem videoItem = waitUploadingItems.get(i);
            VideoItem withStateItem = controller.getDataAccessor().acquire(videoItem.getId());
            //判空,防止待上传项被删除.
            if(withStateItem == null) continue;
            if(withStateItem.getState() == UploadState.STATE_WAIT) {
                controller.onUploadStateChanged(UploadState.STATE_UPLOADING);
                if(!needStopUpload){
                    uploadOne(withStateItem);
                }
            }else if(withStateItem.getState() == UploadState.STATE_UPLOAD_COMPLETE && !waitAddToServerItems.contains(withStateItem)){
                //进到此分支为 已上传成功,但未成功添加至应用服务器的数据
                addVideoToDemoServer(withStateItem);
            }
        }
        if(controller.getDataAccessor().isFullySuccess()){
            controller.onUploadStateChanged(UploadState.STATE_UPLOAD_COMPLETE);
        }
    }



    private boolean uploadOne(final VideoItem videoItem) {
        final Object lock = new Object();

        final File mFile = new File(videoItem.getFilePath());

        try {
            Log.d("uploadtest", "uploadOne()   " + videoItem.getId());
            vcloudUpload =  getUploadBuild(mFile.getName()).build();
            VcloudUpload.UploadInfo uploadContext = new VcloudUpload.UploadInfo(videoItem.getUploadToken(), videoItem.getUploadBucket(), videoItem.getUploadObject());

            if(checkHasSucUpload(videoItem)){
                return true;
            }
            uploadingItem = videoItem;
            String nosUrl = vcloudUpload.uploadFile(mFile, videoItem.getUploadContext(),uploadContext, new VcloudUpload.UploadCallBack() {

                @Override
                public void onUploadInit(VcloudUpload.UploadInfo uploadInfo) {
                    videoItem.setUploadToken(uploadInfo.getUploadToken());
                    videoItem.setUploadBucket(uploadInfo.getUploadBucketName());
                    videoItem.setUploadObject(uploadInfo.getUploadObjectName());
                }

                @Override
                public void onUploadContextCreate(Object o, String s, String s1) {
                    videoItem.setUploadContext(s1);
                    UploadDbHelper.updateToDb(videoItem);
                }

                @Override
                public void onProcess(Object o, long current, long mtotal) {
                    int progress = (int) (current * 100 / (float) mtotal);
                    videoItem.setState(UploadState.STATE_UPLOADING);
                    videoItem.setUploadProgress(progress);
                    controller.onReceiveUploadCallback(videoItem.getId(), progress, UploadState.STATE_UPLOADING);
                }

                @Override
                public void onSuccess(CallRet callRet) {
                    videoItem.setState(UploadState.STATE_UPLOAD_COMPLETE);
                    controller.getDataAccessor().save(videoItem.getId(), videoItem);

                    JSONObject json = JSON.parseObject(callRet.getCallbackRetMsg());
                    if (json!=null && json.getInteger("code") == 200) {
                        long vid = json.getJSONObject("ret").getLong("vid");
                        videoItem.setVid(vid);
                        waitAddToServerItems.add(videoItem);
                        UploadDbHelper.updateToDb(videoItem);
                        addVideoToDemoServer(videoItem);
                    }

                    synchronized (lock){
                        lock.notify();
                    }
                }

                @Override
                public void onFailure(CallRet callRet) {
                    videoItem.setState(UploadState.STATE_UPLOAD_FAIL);
                    controller.onReceiveUploadCallback(videoItem.getId(), 0, UploadState.STATE_UPLOAD_FAIL);
                    UploadDbHelper.updateToDb(videoItem);

                    synchronized (lock){
                        lock.notify();
                    }
                }

                @Override
                public void onCanceled(CallRet callRet) {
                    synchronized (lock){
                        lock.notify();
                    }
                }
            });

            synchronized (lock){
                lock.wait();
            }


            try {
                //sleep 200毫秒  作用: 在断网重连时,让时间片给其他线程进行状态修改等处理
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            uploadingItem = null;

            if(nosUrl!=null) {
                //不显示上传成功状态,直接由上传成功到转码中
                //controller.onReceiveUploadCallback(videoItem.getId(), 100, STATE_UPLOAD_COMPLETE);
            }
        } catch (InvalidParameterException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 检查该任务是否已成功上传, 防止在接近99.99%时,断网或进程被杀,客户端无法感知到已成功上传,故先查询
     * @param videoItem
     * @return true 已成功上传, false未成功上传
     */
    private boolean checkHasSucUpload(final VideoItem videoItem) {

        final Object lock = new Object();

        if(videoItem.getUploadObject()!=null){
            vcloudUpload.uploadQuery(Arrays.asList(new String[]{videoItem.getUploadObject()}), new VcloudUpload.QueryCallBack() {
                @Override
                public void onSuc(List<Integer> list) {
                    if(list!=null && list.size()>0){
                        int vid = list.get(0);
                        if(vid!=0){
                            videoItem.setState(UploadState.STATE_UPLOAD_COMPLETE);
                            controller.getDataAccessor().save(videoItem.getId(), videoItem);
                            videoItem.setVid(vid);
                            waitAddToServerItems.add(videoItem);
                            UploadDbHelper.updateToDb(videoItem);
                            addVideoToDemoServer(videoItem);
                        }
                    }
                    synchronized (lock) {
                        lock.notify();
                    }
                }

                @Override
                public void onFail(String s) {
                    synchronized (lock) {
                        lock.notify();
                    }
                }
            });

            try {
                synchronized (lock) {
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return videoItem.getVid()!=0;

        }else{
            return false;
        }

    }

    private UploadBuilder getUploadBuild(String name) {
        UploadBuilder builder = new UploadBuilder(DemoCache.getContext(), getAppKey(), DemoCache.getAccount(), DemoCache.getVodtoken())
                .setOriginFileName(getValidateFileName(name))
                .setUserFileName(name)
                .setCallbackUrl(DemoServerHttpClient.API_SERVER + TRANSCODE_CALL_BACK)
                .setUploadCallbackUrl(DemoServerHttpClient.API_SERVER + UPLAOD_CALL_BACK );
        return builder;
    }

    /**
     * NOS对原始文件名做了限制,含非法字符将无法上传,故对其进行过滤
     * @param name
     * @return
     */
    private String getValidateFileName(String name) {
        int index_of_p =  name.lastIndexOf('.');
        return System.currentTimeMillis() + name.substring(index_of_p);
    }

    /**
     * 在应用服务器 标记 视频上传成功,服务器成功后,自动开始转码.
     * @param videoItem
     */
    private void addVideoToDemoServer(final VideoItem videoItem) {
        transCodeQueryHandler.post(new Runnable() {
            @Override
            public void run() {
                DemoServerHttpClient.getInstance().addVideo(videoItem.getVid(), videoItem.getDisplayName(), new DemoServerHttpClient.DemoServerHttpCallback<AddVideoResponseEntity>() {
                    @Override
                    public void onSuccess(AddVideoResponseEntity addVideoResponseEntity) {
                        waitAddToServerItems.remove(videoItem);
                        UploadDbHelper.removeItemFromDb(videoItem);
                        controller.onAddVideoResult(200, videoItem.getId(), addVideoResponseEntity);
                    }

                    @Override
                    public void onFailed(int code, String errorMsg) {
                        Toast.makeText(getApplicationContext(), "addVideo error:" + errorMsg, Toast.LENGTH_SHORT).show();
                        waitAddToServerItems.remove(videoItem);
                        controller.onAddVideoResult(code, videoItem.getId(), null);
                    }
                });
            }
        });

    }

    private int mNotificationId = 100;

    private String appKey;
    private String getAppKey() {
        if(appKey == null){
            appKey = readAppKey();
        }
        return appKey;
    }

    private  String readAppKey() {

        try {
            ApplicationInfo appInfo = DemoCache.getContext().getPackageManager().getApplicationInfo( DemoCache.getContext().getPackageName(), PackageManager.GET_META_DATA);
            if (appInfo != null) {
                return appInfo.metaData.getString("com.netease.nim.appKey");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
